package org.rainwalk.bill.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.rainwalk.bill.api.exception.BizException;
import org.rainwalk.bill.api.formatter.annotation.EnumFormatter;
import org.rainwalk.bill.api.formatter.annotation.EnumFormatters;
import org.rainwalk.bill.api.formatter.annotation.MoneyFormatter;
import org.rainwalk.bill.api.formatter.annotation.TimeFormatter;
import org.rainwalk.bill.api.interceptor.AuthInterceptor;
import org.rainwalk.bill.api.model.ResponseCode;
import org.rainwalk.bill.api.model.SessionUser;
import org.rainwalk.bill.entity.Bill;
import org.rainwalk.bill.mapper.BillMapper;
import org.rainwalk.bill.model.biz.AlipayBill;
import org.rainwalk.bill.model.biz.WechatBill;
import org.rainwalk.bill.model.enums.BillChargeTypeEnum;
import org.rainwalk.bill.model.enums.BillStateEnum;
import org.rainwalk.bill.model.enums.BillTypeEnum;
import org.rainwalk.bill.model.po.BillQueryPO;
import org.rainwalk.bill.model.po.BillStatisticsPO;
import org.rainwalk.bill.model.vo.AddBillVO;
import org.rainwalk.bill.model.vo.BillListVO;
import org.rainwalk.bill.model.vo.BillStatisticsVO;
import org.rainwalk.bill.service.IBillService;
import org.rainwalk.bill.util.EncodeUtil;
import org.rainwalk.bill.util.IdGenerator;
import org.rainwalk.bill.util.MyCsvReader;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.SessionAttribute;
import org.springframework.web.multipart.MultipartFile;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * <p>
 * 账单 服务实现类
 * </p>
 *
 * @author 趁雨行
 * @since 2021-02-18
 */
@Slf4j
@Service
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class BillServiceImpl extends ServiceImpl<BillMapper, Bill> implements IBillService {

    private final BillMapper billMapper;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public AddBillVO importWechatBill(MultipartFile csv, Boolean ignoreDuplicate, String appUserId) {
        MyCsvReader reader = new MyCsvReader("----------", null);
        Map<String, Integer> wechatHead = getWechatHead();
        List<WechatBill> wechatBills = getBills(csv, reader, wechatHead, WechatBill.class, EncodeUtil.getEncode(csv));

        AddBillVO billVO = new AddBillVO(wechatBills.size());
        if (wechatBills.isEmpty()) {
            return billVO;
        }

        //落库
        billVO.setBatchNo(IdGenerator.generateId());
        Long nonNeutralBillCount = wechatBills.stream()
                .filter(item -> StrUtil.isNotBlank(item.getChargeType()))
                .count();
        billVO.setNonNeutralBill(nonNeutralBillCount);
        wechatBills.forEach(item -> {
            Bill bill = item.toBill(appUserId, billVO.getBatchNo());
            if (tryAddBill(bill)) {
                billVO.successIncrease(1);
            } else {
                billVO.duplicateIncrease(1);
            }
        });
        return billVO;
    }

    private boolean tryAddBill(Bill bill) {
        if (billMapper.updateWhenNoExists(bill) != 1) {
            return false;
        }
        fillSuitableCategory(bill);
        return true;
    }

    /**
     * 通过历史账单记录填充合适的分类
     */
    private void fillSuitableCategory(Bill bill) {
        Integer timePoint = bill.getCreateTimePoint();
        LocalTime localTimePoint = LocalTime.of(timePoint / 10000, timePoint % 10000 / 100, timePoint % 100);
        DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("Hmmss");
        int startTimePoint = Integer.parseInt(localTimePoint.minusHours(1).format(timeFormatter));
        int endTimePoint = Integer.parseInt(localTimePoint.plusHours(1).format(timeFormatter));

        LambdaQueryWrapper<Bill> queryWrapper = new QueryWrapper<Bill>().lambda()
                .eq(Bill::getTransactionType, bill.getTransactionType())
                .eq(Bill::getOtherParty, bill.getOtherParty())
                .eq(Bill::getChargeType, bill.getChargeType())
                .eq(Bill::getBillType, bill.getBillType())
                .eq(Bill::getUserId, bill.getUserId())
                .between(Bill::getCreateTimePoint, Math.min(startTimePoint, endTimePoint), Math.max(startTimePoint, endTimePoint))
                .isNotNull(Bill::getCategoryId);
        List<Bill> suitableBill = this.list(queryWrapper);
        if (suitableBill.size() == 0) {
            return;
        }

        if (suitableBill.size() == 1) {
            bill.setCategoryId(suitableBill.get(0).getCategoryId());
            log.info("为账单[{}]分配合适的类别[{}]，适配账单[{}]", bill.getId(), bill.getCategoryId(), suitableBill.get(0).getId());
            this.updateById(bill);
            return;
        }

        //匹配到多个账单，匹配一天内时间最近的
        Bill bestMatch = null;
        int minInterval = Integer.MAX_VALUE;
        for (Bill item : suitableBill) {
            int interval = Math.abs(item.getCreateTimePoint() - bill.getCreateTimePoint());
            if(interval < minInterval) {
                minInterval = interval;
                bestMatch = item;
            }
        }

        if (bestMatch != null) {
            bill.setCategoryId(bestMatch.getCategoryId());
            log.info("为账单[{}]分配合适的类别[{}]，适配账单[{}]", bill.getId(), bill.getCategoryId(), bestMatch.getId());
            this.updateById(bill);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public AddBillVO importAlipayBill(MultipartFile csv, Boolean ignoreDuplicate, String appUserId) {
        MyCsvReader reader = new MyCsvReader("----------", "--------");
        Map<String, Integer> alipayHead = getAlipayHead();
        List<AlipayBill> alipayBills = getBills(csv, reader, alipayHead, AlipayBill.class, EncodeUtil.getEncode(csv));

        AddBillVO billVO = new AddBillVO(alipayBills.size());
        if (alipayBills.isEmpty()) {
            return billVO;
        }

        //落库
        billVO.setBatchNo(IdGenerator.generateId());
        long nonNeutralBillCount = alipayBills.stream()
                //统计中性交易
                .filter(item -> StrUtil.isNotBlank(item.getChargeType()))
                .count();
        billVO.setNonNeutralBill(nonNeutralBillCount);
        alipayBills.forEach(item -> {
            Bill bill = item.toBill(appUserId, billVO.getBatchNo());
            if (tryAddBill(bill)) {
                billVO.successIncrease(1);
            } else {
                billVO.duplicateIncrease(1);
            }
        });
        return billVO;
    }


    @Override
    public void update(Bill bill) {
        if (!this.updateById(bill)) {
            throw new BizException(ResponseCode.PARAMS_VERITY_FAIL, "操作失败");
        }
    }

    private <T> List<T> getBills(MultipartFile csv, MyCsvReader reader, Map<String, Integer> head, Class<T> clazz, Charset charset) {
        try (BufferedReader file = new BufferedReader(new InputStreamReader(csv.getInputStream(), charset))) {
            return reader.read(file, clazz, head);
        } catch (IOException e) {
            throw new BizException(ResponseCode.PARAMS_VERITY_FAIL, "文件解析失败");
        }
    }

    private Map<String, Integer> getAlipayHead() {
        Map<String, Integer> headMap = new HashMap<>();
        headMap.put("交易号", 0);
        headMap.put("商家订单号", 1);
        headMap.put("交易创建时间", 2);
        headMap.put("付款时间", 3);
        headMap.put("最近修改时间", 4);
        headMap.put("交易来源地", 5);
        headMap.put("类型", 6);
        headMap.put("交易对方", 7);
        headMap.put("商品名称", 8);
        headMap.put("金额（元）", 9);
        headMap.put("收/支", 10);
        headMap.put("交易状态", 11);
        headMap.put("服务费（元）", 12);
        headMap.put("成功退款（元）", 13);
        headMap.put("备注", 14);
        headMap.put("资金状态", 15);
        return headMap;
    }

    private Map<String, Integer> getWechatHead() {
        Map<String, Integer> headMap = new HashMap<>();
        headMap.put("交易时间", 0);
        headMap.put("交易类型", 1);
        headMap.put("交易对方", 2);
        headMap.put("商品", 3);
        headMap.put("收/支", 4);
        headMap.put("金额(元)", 5);
        headMap.put("支付方式", 6);
        headMap.put("当前状态", 7);
        headMap.put("交易单号", 8);
        headMap.put("商户单号", 9);
        headMap.put("备注", 10);
        return headMap;
    }

    @Override
    public Page<BillListVO> queryPage(BillQueryPO po) {
        int total = this.billMapper.queryPageTotal(po);
        Page<BillListVO> page = new Page<>(po.getPageNum(), po.getPageSize(), total);
        if (total == 0) {
            return page;
        }
        List<BillListVO> bills = this.billMapper.queryPage(po);
        page.setRecords(bills);
        return page;
    }

    @Override
    public List<BillListVO> queryList(BillQueryPO po) {
        return this.billMapper.queryList(po);
    }

    @Override
    @MoneyFormatter(fields = {"amount", "serviceFee", "refundAmount"})
    @TimeFormatter(fields = {"createTime"})
    @EnumFormatters({
            @EnumFormatter(field = "chargeType", enumClazz = BillChargeTypeEnum.class),
            @EnumFormatter(field = "state", enumClazz = BillStateEnum.class),
            @EnumFormatter(field = "billType", enumClazz = BillTypeEnum.class)
    })
    public List<Map<String, Object>> queryListFormatter(BillQueryPO po) {
        List<BillListVO> vos = this.queryList(po);
        return vos.stream().map(vo -> BeanUtil.beanToMap(vo)).collect(Collectors.toList());
    }

    @Override
    public BillStatisticsVO queryStatistics(BillQueryPO po) {
        BillStatisticsPO statisticsPO = this.billMapper.queryStatistics(po);
        return statisticsPO.toVO();
    }

    @Override
    public List<Bill> getByBatchNoAndUser(String batchNo, String appUserId) {
        LambdaQueryWrapper<Bill> queryWrapper = new LambdaQueryWrapper<Bill>()
                .eq(Bill::getBatchNo, batchNo)
                .eq(Bill::getUserId, appUserId);
        return this.list(queryWrapper);
    }

    @Override
    public Bill getByIdAndUser(@PathVariable String id, String appUserId) {
        LambdaQueryWrapper<Bill> queryWrapper = new LambdaQueryWrapper<Bill>()
                .eq(Bill::getId, id)
                .eq(Bill::getUserId, appUserId);
        return this.getOne(queryWrapper);
    }

    @Override
    public void updateUserRemark(Bill bill) {
        this.billMapper.updateUserRemark(bill);
    }

    @Override
    public void updateCategory(Bill bill) {
        this.billMapper.updateCategory(bill);
    }

    /**
     * 更新订单信息
     *
     * @param bill
     */
    @Override
    public void updateBill(Bill bill) {
        this.billMapper.updateBill(bill);
    }
}
